<template>
  <div v-if="data.controlTypeID != ''" class="OctopusControl">
    <!-- TODO:由iControl识别验证通过与否返回控制内部控件的样式为最简单的方式。 -->
    <div
      class="iControl"
      v-bind:class="
        (data.validate ? '' : 'iError ') +
          (data.disabled ? 'iDisabled ' : '') +
          ('C_' + data.controlTypeID)
      "
    >
      <!-- Element-UI Popover 面板-->
      <!--      {{modelValue}}-->
      <el-popover
        placement="top-start"
        title="Tips"
        trigger="click"
        width="100%"
        close-delay="200"
      >
        <template>
          <div
            class="el-popover__content"
            v-html="data.note ? data.note.replace(/[\n\r]/g, '<br>') : ''"
          ></div>
        </template>
        <!--  TODO: 利用下面的if判断控件类型，从而渲染不同的控件，element-ui针对特殊控件均进行了封装，直接绑定v-model即可。-->
        <!-- 下拉框 | 复选下拉框 -->
        <template v-if="data.controlTypeID == 3 || data.controlTypeID == 11">
          <el-select
            v-model="modelValue"
            remote
            reserve-keyword
            clearable
            filterable
            :multiple="data.controlTypeID == 3 ? false : true"
            v-bind="{ disabled: data.disabled, placeholder: data.placeholder }"
            :remote-method="request"
            :loading="loading"
            v-on:visible-change="selectPanelVisible"
            v-on:change="change"
            slot="reference"
          >
            <el-option
              v-for="item in requestData"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
              <span style="float: left">{{ item.label }}</span>
              <!--            <span style="float: right; color: #8492a6; font-size: 13px">{{ item.value }}</span>-->
            </el-option>
          </el-select>
        </template>

        <!-- 日历框 | 日历时间框 | 日历月框 -->
        <template
          v-else-if="
            data.controlTypeID == 5 ||
              data.controlTypeID == 6 ||
              data.controlTypeID == 17
          "
        >
          <el-date-picker
            v-model="modelValue"
            slot="reference"
            :value-format="
              data.controlTypeID == '5'
                ? 'yyyy-MM-dd'
                : data.controlTypeID == '6'
                ? 'yyyy-MM-dd HH:mm:ss'
                : 'yyyy-MM'
            "
            v-bind="{
              disabled: data.disabled,
              type:
                data.controlTypeID == '5'
                  ? 'date'
                  : data.controlTypeID == '6'
                  ? 'datetime'
                  : 'month'
            }"
            placeholder="选择日期"
            v-on:change="change"
          ></el-date-picker>
        </template>

        <!-- 数字框 | 小数点框 -->
        <template
          v-else-if="data.controlTypeID == 12 || data.controlTypeID == 13"
        >
          <!--        {{modelValue}} / {{value}}-->
          <!--        {{data.null}}|| {{data.defaultValue}}||{{modelValue}}-->
          <el-input-number
            v-model="modelValue"
            v-on:change="change"
            :min="data.minLength"
            :max="data.maxLength"
            slot="reference"
            v-bind="{
              disabled: data.disabled,
              precision: data.controlTypeID == 12 ? 0 : 2,
              step: data.controlTypeID == 12 ? 1 : 0.1
            }"
          ></el-input-number>
        </template>

        <!-- 图像上传 | 文件上传 -->
        <template
          v-else-if="data.controlTypeID == 15 || data.controlTypeID == 16"
        >
          <!--         {{modelValue}} <br/><br/> {{uploadFileList}}-->
          <div class="Uploader" slot="reference">
            <!--        <el-input v-model="modelValue" v-on:change="change" autosize show-word-limit v-bind="{type:'textarea'}"-->
            <!--                  v-show="true"></el-input>   :on-change="uploaderChange" -->
            <el-upload
              action="#"
              :list-type="data.controlTypeID == 15 ? 'picture-card' : 'text'"
              :auto-upload="false"
              :on-preview="uploaderPreview"
              :multiple="true"
              :file-list="uploadFileList"
              :before-remove="uploaderBeforeRemove"
              :on-remove="uploaderRemove"
              :limit="data.maxLength"
              :on-change="uploaderChange"
              :on-exceed="uploaderExceed"
            >
              <!-- <i class="el-icon-plus">上传文件</i> -->
              <template v-if="data.controlTypeID == 15">
                <i class="el-icon-plus img-upload-button"></i>
              </template>
              <template v-else>
                <el-button
                  class="file-upload-button"
                  size="small"
                  type="primary"
                  >上传文件</el-button
                >
              </template>
            </el-upload>
            <el-progress
              v-show="showUploadProgress"
              :percentage="UploadProgressPercentage"
              :color="'#428bca'"
              :stroke-width="2"
            />
          </div>

          <!-- 图像预览控件，要移动到body下才行，否则会和Bootstrap冲突。 -->
          <el-dialog :visible.sync="showUploaderViewPanel">
            <img width="100%" :src="uploaderViewPath" alt="" />
          </el-dialog>
        </template>

        <!-- 富文本框 -->
        <template v-else-if="data.controlTypeID == 18">
          <!--          <o-control-rich-text v-model="modelValue" v-on:change="change" v-bind="{source:data}"-->
          <!--                               slot="reference"></o-control-rich-text>-->
          <quill-editor
            v-model="modelValue"
            slot="reference"
            :options="editorOption"
            v-on:blur="blur"
            v-on:focus="focus"
            v-on:change="change"
            v-on:ready="ready"
          >
          </quill-editor>
          <div slot="reference" class="editorCount">
            <span
              v-if="modelValue"
              v-bind:class="modelValue.length > data.maxLength ? 'outsize' : ''"
              >{{ modelValue.length }}</span
            >
            <span v-else>0</span>/{{ data.maxLength }}
          </div>
          <input
            type="file"
            ref="quill_uploader"
            accept="image/*"
            multiple
            @change="onEditorImageUpload"
            v-show="false"
          />
        </template>

        <!--      &lt;!&ndash; 开关框 &ndash;&gt;-->
        <!--      <template v-else-if="data.controlTypeID == ">{{modelValue}}-->
        <!--        <el-switch style="display: block" v-model="modelValue" active-color="#13ce66" inactive-color="#ff4949" v-on:change="change">-->
        <!--        </el-switch>-->
        <!--      </template>-->

        <template v-else>
          <!--  autosize 加上这个以后，rows 高度设置将无效化 -->
          <el-input
            v-model="modelValue"
            v-on:change="change"
            :show-word-limit="data.model == 'ITEM' ? true : false"
            slot="reference"
            v-bind="{
              disabled: data.disabled,
              type: data.controlTypeID == '2' ? 'textarea' : 'text',
              maxlength: data.maxLength,
              rows: data.height / 20,
              placeholder: data.placeholder
            }"
          ></el-input>
          <!-- <input v-model.lazy="modelValue" v-bind="{disabled:data.disabled}"/> -->
        </template>
      </el-popover>
    </div>
  </div>
  <div v-else>
    <label class="iLabel iError">Control Type Is Not Found</label>
  </div>
</template>

<script>
import Quill from "quill";
// import * as Quill from 'quill'  //引入编辑器
// import undo_icon from "quill/assets/icons/undo.svg";
// import redo_icon from "quill/assets/icons/redo.svg";
// let icons = window.Quill.import("ui/icons");
// icons["undo"] = undo_icon;
// icons["redo"] = redo_icon;

let fonts = [
  "Microsoft-YaHei",
  "SimHei",
  "KaiTi",
  "SimSun",
  "FangSong",
  "WaWa",
  "Arial"
];
let Font = window.Quill.import("formats/font");
Font.whitelist = fonts; //将字体加入到白名单
window.Quill.register(Font, true);

let fontSize = ["10px", "12px", "14px", "16px", "20px", "24px", "36px"];
Quill.imports["attributors/style/size"].whitelist = fontSize;
Quill.register(Quill.imports["attributors/style/size"]);
Quill.register(fontSize, true);

export default {
  name:
    "ControlEngine" /* 这个name暂时不知道用啥用，根据官方文档说的是方便排错的 */,
  data() {
    return {
      octopus: this.$parent.octopus,
      // 这个属性待测试自定义model成功以后即可删除。
      dataExtend: null,
      // v-model模式必须使用value，这个是vue的保留字段，不能改名，内部已经通过prop属性传过来了。
      modelValue: this.value,

      showRequestPanel: false, // 查询面板是否展开。
      loading: false,
      requestData: [],

      showUploaderViewPanel: false, // 上传控件预览面板显示
      uploaderViewPath: "", // 上传控件预览地址
      uploadFileList: [], // 上传控件文件清单列表
      // uploadFileMaxSize: 4096 * 1000,  // 文件最大大小，目前是 4MB。支持大文件后这个由height控制，单位MB。
      showUploadProgress: false,
      UploadProgressPercentage: 0,
      UploadProgressData: [], // 文件上传进度计算，支持多文件时的百分比进度。

      editorContent: null,
      // 编辑器参数
      editorOption: {
        modules: {
          toolbar: {
            container: [
              ["undo", "redo"], // ['undo', 'redo', 'hello'] 比如hello可以自定义样式
              [{ font: fonts }], // 字体
              [{ header: [1, 2, 3, 4, 5, 6, false] }], // 段落大小
              // [{'size': ['small', false, 'large', 'huge']}],  // custom dropdown 文字大小，但是会延续到下一行。
              [{ size: fontSize }],
              ["bold", "italic", "underline", "strike"], // 加粗，斜体，下划线，删除线
              [{ color: [] }, { background: [] }], // 颜色、背景色
              [{ align: [] }], // 居中
              [{ indent: "-1" }, { indent: "+1" }], // 缩进
              ["link", "image", "video"], // 超链接、图片
              [{ list: "ordered" }, { list: "bullet" }], // 有序列表、无序列表
              [{ script: "sub" }, { script: "super" }], // 下标、上标
              [{ direction: "rtl" }], // 居左、居右
              // ['blockquote', 'code-block'],              // 块代码，代码

              // [{'header': 1}, {'header': 2}],               // custom button values
              ["clean"]
            ],
            // 委托事件，委托后，quill的事件将不起任何作用。
            handlers: {
              image: this.onEditorImageEvent,
              redo: this.onEditorRedoEvent,
              undo: this.onEditorUndoEvent
            }
          },
          history: {
            delay: 1000,
            maxStack: 50,
            userOnly: false
          },
          // imageDrop: true,
          imageResize: {
            displayStyles: {
              backgroundColor: "black",
              border: "none",
              color: "white"
            },
            resizeStyle: {
              backgroundColor: "red"
            },
            modules: ["Resize", "DisplaySize", "Toolbar"]
          }
        },
        placeholder: this.data.placeholder // 也可以通过样式来定义
        // placeholder: "place holder...",
      }
    };
  },
  created: function() {
    this.init();
    // console.error(this.data.displayName + ":" + this.data.placeholder);
    this.octopus.tips.debug("Control.vue执行了created事件。");
  },
  // 当前控件挂载完成以后调用，因为ControlEngine每次进来都会触发Created事件，所以mounted也是能被触发的。
  mounted: function() {
    this.validate();
    this.bind();
    // console.log("data create:" + JSON.stringify(this.data));
    // 如果你希望整个同步DOM（不包含异步）都挂载完成，则需要写在nextTick事件中。
    this.$nextTick(function() {
      // 将图像预览控件移动到Body下，否则会和Bootstrap冲突。
      if (this.data.controlTypeID == 15) {
        $("body").append($(".el-dialog__wrapper"));
      }
      this.octopus.tips.debug("Control.vue执行了mounted.$nextTick事件。");
    });
    this.octopus.tips.debug("Control.vue执行了mounted事件。");
  },
  beforeUpdate: function() {
    // this.octopus.tips.debug("Control.vue执行了beforeUpdate事件。");
  },
  updated: function() {
    // 基于VUE的特性，当控件发生变化则触发验证。
    this.validate();
    // this.octopus.tips.debug("Control.vue执行了Updated事件。");
  },
  // 活动事件，写在这里的好处是不管是第一次创建还是后退回来，都会触发这个事件
  // 比较典型的就是列表组件，返回的时候往往是需要刷新的。
  activated: function() {
    // this.$nextTick(function () {
    //   this.octopus.tips.debug("Control执行了activated.$nextTick事件。");
    // });
    this.octopus.tips.debug("Control.vue执行了activated事件。");
  },
  deactivated: function() {
    this.octopus.tips.debug("Control.vue执行了deactivated事件。");
  },
  computed: {
    // 如果cellData数据发生了改变,则触发这个事件。
    chaeCell: function() {
      // return this.data.id;
    }
  },
  methods: {
    init: function() {
      this.initData();
    },
    // 初始化数据，此处value还没有渲染给modelValue，可对值做初始化及格式化
    initData: function() {
      // 记录变更，此处适配页面跳转进来，没有走监听的。
      this.dataExtend = $.extend(true, {}, this.data);

      // 数字框 | 小数点框
      if (this.data.controlTypeID == "12" || this.data.controlTypeID == "13") {
        // 解决ElementUI的数字框，当值为null的时候显示0的bug，要显示 undefined 才是显示空。
        this.modelValue = this.octopus.tools.IsNullOrEmpty(this.modelValue)
          ? undefined
          : this.modelValue;
      }
      // 图像上传框 | 文件上传框
      else if (
        this.data.controlTypeID == "15" ||
        this.data.controlTypeID == "16"
      ) {
        // alert(this.data.controlTypeID);
      }
      // 富文本控件，初始化请在ready事件中实现，此处init太早期，同理未来其他的三方控件如果提供了ready事件，请遵循规则在他们的ready事件中进行绑定和实现。
      else if (this.data.controlTypeID == "18") {
        // 如果为空，则赋值空。
        this.modelValue = this.octopus.tools.IsNullOrEmpty(this.modelValue)
          ? ""
          : this.modelValue;
        // this.octopus.control.renderRichTextBox(".RichTextBox");
        // this.modelValue = "<p><img src=\"https://api.herodoo.com:447/api/upload/20211223/b3ccf321-c81d-47f0-8705-2311d835f3a5\" width='258'></p>";
      }
    },
    // 数据格式化及绑定，根据控件类型进行对应的数据修正，以及前端八爪鱼代码的解析，比如#{param=order}，则获取URL参数为orderID的值
    // 此处bind还会产生一个效果，就是原来vue的属性会自动隐藏掉。。。有点高级。。。。
    bind: function() {
      let o = this;
      // 获取当前控件
      let control = $(this.$el);
      let controlTypeID = this.data.controlTypeID;
      // 统一绑定Tips备注
      control.attr("data-original-title", "Tips");
      control.attr("data-toggle", "popover");
      control.attr("data-placement", "top");
      control.attr("data-trigger", "hover");
      control.attr("data-content", this.data.note);

      // 初始化完后的绑定动作【下拉框】【复选下拉框】的默认数据。
      if (controlTypeID == "3" || controlTypeID == "11") {
        // 新页面时需要这个配置，直接弹出不需要下面这个配置。
        this.request("", this.modelValue, true);
      }
      // 图像上传控件 || 文件上传控件
      else if (controlTypeID == "15" || controlTypeID == "16") {
        if (!this.octopus.tools.IsNullOrEmpty(this.modelValue)) {
          this.uploadFileList = JSON.parse(this.modelValue);
        }
      }
      // 富文本控件
      else if (controlTypeID == "18") {
      }
    },
    // 控件事件注册，目前处于调试阶段，因为quill而注册。
    blur: function(data) {
      console.log("Control Blur:");
      console.log(data);
      // data.enable(false);
    },
    // 控件事件注册，目前处于调试阶段，因为quill而注册。
    focus: function(data) {
      console.log("Control Focus:");
      console.log(data);
      // data.enable(false);
    },
    // 准备完成，注册全局控制控件，控件事件注册
    ready: function(dom) {
      console.log("Control Ready:");

      // 富文本准备事件，针对提供ready的控件进行适配，因为加载特性，不能在bind事件中注册的可以在这里兼容。
      if (this.data.controlTypeID == "18") {
        // 定义编辑器全局变量，其实dom已经获取到该对象。
        this.editorContent = dom; // 此处的 dom 等价于 this.$refs.richBox.quill;
        let container = this.editorContent.container;
        // 设置高度
        container.style.height = this.data.height + "px";

        // 设置禁用
        if (this.data.disabled) {
          container.style.background = "#f5f7fa"; // 设置禁用色
          // $(container).find(".ql-editor").css("contenteditable", false);
          // $(container).find(".ql-editor").css("background", "blue");
          this.editorContent.enable(false); // 禁用编辑器
        }
        // console.log(this.editorContent);
      }
    },
    // 完美解决自定义控件的问题！采用change曲线救国！！
    change: function(value) {
      // 控件值兼容性调整。
      // 日历月框，并且不为空才进行格式化
      if (
        this.data.controlTypeID == "17" &&
        !this.octopus.tools.IsNullOrEmpty(value)
      ) {
        value =
          this.modelValue.split("-")[0] +
          "-" +
          this.modelValue.split("-")[1] +
          "-01";
      }
      // 富文本控件双向数据绑定机制。
      else if (this.data.controlTypeID == "18") {
        value = value.html; // 方式1，quill的change返回value包含3个对象，html、text、quill对象。
        // let html =   this.editorContent.container.innerHTML;
        // console.log(html);
        // alert("LL:"+value);
      }

      // 触发绑定事件
      if (this.data.model == "LIST") {
        let key = (this.data.searchKey + "").toLocaleUpperCase();
        // 如果是范围查询，扩展searchValue的数据值为2个。
        if (key == "BETWEEN") {
          this.data.searchValue[this.index || 0] = value;
        }
        // 否则由控件组成的数组是直接返回的。
        else {
          this.data.searchValue = value;
        }

        this.octopus.tips.debug(
          "[change]" +
            this.data.name +
            " 的 [searchValue] 发生了变更，由 " +
            JSON.stringify(this.dataExtend.searchValue) +
            " 变更为 " +
            JSON.stringify(this.data.searchValue)
        );
      } else {
        this.data.defaultValue = value;

        this.octopus.tips.debug(
          "[change]" +
            this.data.name +
            " 的 [defaultValue] 发生了变更，由 " +
            JSON.stringify(this.dataExtend.defaultValue) +
            " 变更为 " +
            JSON.stringify(this.data.defaultValue)
        );
      }

      if (this.changed) {
        // 范围比较时需要判断0和1都需要有值
        if (this.data.searchKey == "BETWEEN") {
          // 只有当2个值不为空 或者 都为空 才触发。
          if (
            (!this.octopus.tools.IsNullOrEmpty(this.data.searchValue[0]) &&
              !this.octopus.tools.IsNullOrEmpty(this.data.searchValue[1])) ||
            (this.octopus.tools.IsNullOrEmpty(this.data.searchValue[0]) &&
              this.octopus.tools.IsNullOrEmpty(this.data.searchValue[1]))
          ) {
            this.changed(this.data, this.dataExtend);
          }
        } else {
          this.changed(this.data, this.dataExtend);
        }
      }
      this.dataExtend = $.extend(true, {}, this.data);
    },
    // 弹出面板时触发查询，前提时没有下啦搜索数据
    selectPanelVisible: function(value) {
      this.showRequestPanel = value;
      if (value) {
        // 展开面板，默认加载全部，使用起来方便，因为默认加载时候是带条件的。
        this.request("", "");
      }
    },
    // 解码、格式化、验证 多步一体。
    validate: function() {
      let control = $(this.$el);
      // 状态默认开启
      let result = true;
      // 只有Item模式需要验证，List大多是下拉，模糊查询，所以没法做数据格式验证。
      if (this.data.model == "ITEM") {
        // 不允许非空，则判定。
        if (!this.data.null) {
          if (this.octopus.tools.IsNullOrEmpty(this.modelValue)) {
            result = false;
          }
          // 其他控件
          else {
            // 图像上传控件 || 文件上传控件
            if (
              this.data.controlTypeID == "15" ||
              this.data.controlTypeID == "16"
            ) {
              if (JSON.parse(this.modelValue).length == 0) {
                result = false;
              }
            }
          }
        }

        // 长度验证 判定
        if (
          this.data.maxLength &&
          !this.octopus.tools.IsNullOrEmpty(this.modelValue)
        ) {
          // 富文本控件
          if (this.data.controlTypeID == "18") {
            //alert(this.modelValue);
            // 如果字符长度大于设置长度。
            if (this.modelValue.length > this.data.maxLength) {
              result = false;
            }
          }
        }

        // 非空验证成功 并且 值不为空才进入，因为下面的验证都是针对数据的验证。
        if (result && !this.octopus.tools.IsNullOrEmpty(this.modelValue)) {
          // 数据格式化，日期取消，因为会触发一次change事件，没有意义，故放到后台处理
          // 日历框
          // if (this.data.controlTypeID == 5) {
          //   if (!this.octopus.tools.IsNullOrEmpty(this.data.defaultValue)) {
          //     this.data.defaultValue = this.octopus.tools.dateFormatToString(controlValue, "yyyy-MM-dd");
          //   }
          // }
          // // 日历时间框
          // else if (this.data.controlTypeID == 6) {
          //   if (!this.octopus.tools.IsNullOrEmpty(this.data.defaultValue)) {
          //     this.data.defaultValue = this.octopus.tools.dateFormatToString(controlValue, "yyyy-MM-dd HH:mm:ss");
          //   }
          // }
          // 邮件验证
          if (this.data.controlTypeID == 8) {
            let regex = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
            result = regex.test(this.modelValue);
          }
          // 邮政编码框验证
          else if (this.data.controlTypeID == 9) {
            // 正则表达式，^开头，$结束，[\d]纯数字，{6}限制6位长度，范围可通过{6,10}。
            // http://tools.jb51.net/regex/create_reg
            let regex = /^[\d]{6}$/;
            // 查看匹配项
            // console.log(parseData.match(regex));
            // 查看
            result = regex.test(this.modelValue);
          }
          // 网址验证
          else if (this.data.controlTypeID == 10) {
            let regex = /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/;
            result = regex.test(this.modelValue);
          }
          // // 数字框
          // else if (this.data.controlTypeID == 12) {
          //   let regex = /^[\d]*$/;
          //   result = regex.test(this.modelValue);
          // }
          // // 小数点框（浮点）
          // else if (this.data.controlTypeID == 13) {
          //   let regex = /^[\d]*[.]{1}[\d]*$/;
          //   result = regex.test(this.modelValue);
          // }

          // 邮编框只能输入邮编
        }
      }
      // LIST模式下禁止验证禁用，默认false
      else {
        this.data.disabled = false;
      }

      this.data.validate = result;
    },
    // 请求
    request: function(labelData, valueData, isFrist) {
      // console.log("========name:" + this.data.name + "[" + labelData + "][" + valueData + "]");
      let o = this;
      this.loading = true;

      let control_request_celldata;
      // 自动化封装
      if (!this.octopus.tools.IsNullOrEmpty(labelData)) {
        control_request_celldata = [
          {
            name: this.data.controlSourceText,
            searchKey: "LIKE",
            searchValue: labelData
          },
          { name: this.data.controlSourceValue }
        ];
      } else if (!this.octopus.tools.IsNullOrEmpty(valueData)) {
        control_request_celldata = [
          {
            name: this.data.controlSourceText
          },
          {
            name: this.data.controlSourceValue,
            searchKey: "IN",
            searchValue: valueData
          }
        ];
      } else {
        control_request_celldata = [
          {
            name: this.data.controlSourceText
          },
          { name: this.data.controlSourceValue }
        ];
      }

      // 判断是否有关联数据，如果有则进行数据过滤。
      if (this.data.link) {
        $(this.data.link).each(function(index, item) {
          // 判断值是否存在，如果不存在，则设置为-1，保证查不到数据。
          item.value = o.octopus.tools.IsNullOrEmpty(item.value)
            ? -1
            : item.value;
          // 得益于八抓鱼API框架，后台处理参数时，会自动识别 值 和 数组，所以这里直接赋值就可以了。
          control_request_celldata.push({
            name: item.mapping,
            searchKey: "IN",
            searchValue: item.value
          });

          // 首次加载不启用判断。
          if (item.value == -1 && !isFrist) {
            o.octopus.tips.warning("请先选择" + item.name);
          } else {
            // o.octopus.tips.warning(JSON.stringify(item));
          }
          //alert(JSON.stringify(control_request_celldata));
        });
      }

      // 请求后台八爪鱼API框架。
      this.octopus.request.api.post(
        "/Octopus/Engine/List",
        {
          id: this.data.controlSourceID,
          // 当 data.model 不存在时，采用树状请求模式，存在值时，采用标准模式。
          parentKey:
            this.data.model == undefined
              ? this.data.controlSourceParentKey
              : null,
          parentValue:
            this.data.model == undefined
              ? this.data.controlSourceParentValue
              : null,
          orderData: this.data.controlSourceOrder,
          cellData: control_request_celldata,
          pageData: { rowSize: 50 } // 性能考虑及用户体验，查询数据扩展至返回最多50条最近的。
        },
        // 处理回调事件，通过分析data.status.code的状态来实现。
        function(result) {
          if (result.status.code == 200) {
            o.requestData = [];
            // console.error(JSON.stringify(o.data));
            // 层级字段才显示
            if (o.data.hierarchy) {
              o.requestData.push({
                // label: "=== 请选择 " + o.data.displayName + " ===",
                label: "=== 顶层 ===",
                // 因为Element-UI框架的问题，必须字符串，否则不能默认绑定。。。好傻。。所以要转换一下。
                value: "0"
              });
            }
            // 回调封装数据
            $(result.content.queryData).each(function(index, item) {
              // 因为ElementUI识别数据类型，所以为了全适配，text和value必须都为String类型，否则会因为类型不正确匹配不上，切记。
              o.requestData.push({
                label: item[o.data.controlSourceText],
                // 因为Element-UI框架的问题，必须字符串，否则不能默认绑定。。。好傻。。所以要转换一下。
                value:
                  item[o.data.controlSourceValue] != null
                    ? item[o.data.controlSourceValue].toString()
                    : item[o.data.controlSourceValue]
                // value: item[o.data.controlSourceValue].toString(),
              });
            });
          } else {
            o.octopus.tips.error(result);
          }
          // 告诉控件，异步请求已经完成
          o.loading = false;
        }
      );
    },
    // 点击文件时，需要判断文件后缀，进行下载或者打开播放，后续这个功能要加强的。
    uploaderPreview(file) {
      // 图像上传，打开预览控件
      if (this.data.controlTypeID == "15") {
        this.uploaderViewPath = file.url;
        this.showUploaderViewPanel = true;
      }
      // 文件上传，调用API弹出下载
      else if (this.data.controlTypeID == "16") {
        // 支持传入八抓鱼API框架体系下的URL文件路径对象，弹出流式下载。
        // console.log(file);
        this.octopus.request.api.download(file, function(data) {
          console.log(JSON.stringify(data));
        });
      }
      // 已经被废弃。
      // JS上传文件，优点是JS可自定义名称，缺点是文件要全部下载到JS的Blob数据库中再弹出，针对大文件很不友好。。会让用户以为点了没反应。
      else {
        /**
           /* JS 利用JS下载到本地，再模拟打开一个下载窗口。。。
           **/
        download(file.url, file.name);

        /**
         * 下载
         *   {String} url 目标文件地址
         *   {String} filename 想要保存的文件名称
         */
        function download(url, filename) {
          getBlob(url, function(blob) {
            saveAs(blob, filename);
          });
        }

        /**
         * 获取 blob
         *   {String} url 目标文件地址
         *  {cb} 下载完的保存回调
         */
        function getBlob(url, cb) {
          let xhr = new XMLHttpRequest();
          xhr.open("GET", url, true);
          xhr.responseType = "blob";
          xhr.onload = function() {
            if (xhr.status === 200) {
              cb(xhr.response);
            }
          };
          xhr.send();
        }

        /**
         * 保存，利用JS下载到本地，再模拟打开一个下载窗口。。。
         *   {Blob} blob
         *   {String} filename 想要保存的文件名称
         */
        function saveAs(blob, filename) {
          if (window.navigator.msSaveOrOpenBlob) {
            navigator.msSaveBlob(blob, filename);
          } else {
            let link = document.createElement("a");
            let body = document.querySelector("body");

            link.href = window.URL.createObjectURL(blob);
            link.download = filename;

            // fix Firefox
            link.style.display = "none";
            body.appendChild(link);

            link.click();
            body.removeChild(link);

            window.URL.revokeObjectURL(link.href);
          }
        }
      }
    },
    // 删除时触发
    uploaderRemove(file, fileList) {
      this.uploadFileList = fileList;
      this.modelValue = JSON.stringify(this.uploadFileList);
      // 删除时也触发change，否则删除不会变更数据源。
      this.change(JSON.stringify(this.uploadFileList));
      this.octopus.tips.warning("移除了：" + file.name + "。");
    },
    // 删除前触发
    uploaderBeforeRemove(file, fileList) {
      // return this.octopus.confirm.success("确定移除图像：" + file.name + "？");
      // return this.$confirm(`确定移除图像： ${file.name}？`);

      // 此处不能用封装过的组件，如果要直接return，就不能使用回调，用了回调，直接return就会无效。。。F**K。。。
      return this.octopus.vue.$confirm(`确定移除： ${file.name}？`);
    },
    // 超过上传数量
    uploaderExceed(file, fileList) {
      this.octopus.tips.error(
        "超出上传限制，本控件只允许上传 " + this.data.maxLength + " 个文件。"
      );
    },

    // 上传时触发，上传5张，会调用5次这个函数。
    uploaderChange: function(file, fileList, callback) {
      // console.log(fileList);
      // alert(JSON.stringify(file));
      let o = this;
      // 重置
      this.showUploadProgress = true;
      this.UploadProgressPercentage = 0;

      // 如果大小在允许范围内，则进入，单位MB。
      if (file.size < this.data.height * 1024 * 1024) {
        // 如果是图像上传，则进行图像压缩后再上传
        // if (this.data.controlTypeID == "15") {
        //   // // 设置图像包数据，这个压缩反而会把文件压大。。。F**K。。。
        //   // let fileData = {
        //   //   size: 0.5, // 尺寸压缩率。
        //   //   quality: 0.1, // 图像质量90%。
        //   //   file: file,
        //   // };
        //   // // 因为element-ui的上传空控件并不返回base64编码的内容，所以全部需要统一的file转base64自己的实现才行。
        //   // this.octopus.tools.imageCompress(fileData, function (compressFile) {
        //   //   // 将压缩后的图像更新对象。
        //   //   upload(compressFile);
        //   // });
        //   upload(file.raw);
        // }
        // // 上传文件
        // else {
        //   upload(file.raw);
        // }

        upload(file.raw);

        // 正式上传
        function upload(fileSource) {
          // alert(content.size);
          // 执行上传
          o.octopus.request.api.upload(
            fileSource,
            // 回调上传进度：百分比（引擎库已经自动计算，满分100分。）
            function(data) {
              // 判断当前文件是否在本次上传集合中，通过find，找到匹配的第一个元素。
              let findData = o.UploadProgressData.find(val => {
                return val.name == data.name;
              });
              // 如果没有找到，则插入。
              if (findData == undefined) {
                o.UploadProgressData.push(data);
              }
              // 否则更新进度数据
              else {
                findData.percentage = data.percentage;
              }

              // 进行平均算法
              let avgPercentage = 0;
              //  遍历叠加
              o.UploadProgressData.map(item => {
                avgPercentage += item.percentage;
              });
              // 求平均
              avgPercentage = avgPercentage / o.UploadProgressData.length;

              // console.log(fileSource);
              // console.log(JSON.stringify(data));
              // console.log(avgPercentage);
              // console.log(JSON.stringify(o.UploadProgressData));

              o.UploadProgressPercentage = o.octopus.tools.numberFormat(
                avgPercentage,
                2
              );
              // 彻底完成后
              if (avgPercentage == 100) {
                // 延迟 1 秒以后再隐藏。
                setTimeout(function() {
                  // 隐藏进度条
                  o.showUploadProgress = false;
                  // 用完重置进度条及上传数据包。
                  o.UploadProgressPercentage = 0;
                  o.UploadProgressData = [];
                }, 1000);
              }

              // 综合等于 100 以后，等待 0.5 秒后再隐藏。
            },
            // 完成后回调
            function(fileData) {
              // console.log(data);
              if (fileData.status.code == 200) {
                // 创建统一格式的文件数据。
                let uploadFileData = {
                  url: o.octopus.config.url + fileData.content.url,
                  name: fileData.content.name
                };
                // 回调上传控件事件 包容图像 和 文件
                if (
                  o.data.controlTypeID == "15" ||
                  o.data.controlTypeID == "16"
                ) {
                  // alert(JSON.stringify(fileData.content));
                  o.uploadFileList.push(uploadFileData);
                  o.change(JSON.stringify(o.uploadFileList));
                }
                // 回调富文本编辑器事件
                else if (o.data.controlTypeID == "18") {
                  // o.onEditorImageUploadCallback(uploadFileData);
                  // 确认是否包含回调
                  if (callback) {
                    callback(uploadFileData);
                  }
                }
              } else {
                console.log("Upload ERROR: " + JSON.stringify(fileData.status));
              }
            }
          );

          console.log("文件大小：" + file.size / 1024 + "KB");
        }
      } else {
        // 弹出提示
        this.octopus.tips.error(
          "文件不能超过" + this.data.height + "MB，上传失败。"
        );
        // 删除fileList
        this.uploadFileList = $.grep(this.uploadFileList, function(item) {
          return item.name != file.name; //筛选出当前数据列数据
        });
      }
    },
    onEditorRedoEvent: function(data) {
      this.editorContent.history.redo();
    },
    onEditorUndoEvent: function(data) {
      this.editorContent.history.undo();
    },
    // 编辑器专用函数
    onEditorImageEvent: function(status) {
      // 没有禁用的情况下触发上传按钮
      if (!this.data.disabled) {
        // document.getElementById("btn_file").click();
        this.$refs.quill_uploader.click();
      }
    },
    // 编辑器上传事件：蒋琪
    onEditorImageUpload: function(fileDom) {
      let fileContent = fileDom.target;
      let o = this;
      $(fileContent.files).each(function(index, file) {
        // 定义八抓鱼标准文件流对象。
        let fileRaw = { name: file.name, raw: file, size: file.size };
        // 调用八抓鱼通用上传事件。
        o.uploaderChange(fileRaw, fileContent.files, function(data) {
          let range = o.editorContent.getSelection();
          console.log(range);
          let length = range.index;
          // 插入图片，res为服务器返回的图片链接地址
          o.editorContent.insertEmbed(length, "image", data.url);
          // 调整光标到最后
          o.editorContent.setSelection(length + 1);
          // 定位光标
          o.editorContent.focus();
        });
      });
    }
  },
  watch: {
    // 监听value，因为有时候控件是二次绑定的，所以需要监听重新绑定，算是个vue的小缺陷吧。
    value(newv, oldv) {
      console.log(
        "[change][watch]" + this.data.name + ":" + oldv + "==>" + newv
      );
      // 此处的更新和行：58 一致。
      this.modelValue = newv;
      //
      // 记录变更，此处适配当前页弹出的，没有走created.init()的。
      this.dataExtend = $.extend(true, {}, this.data);

      // 如果值发生更改，且下拉框或者复选框，则需要重新绑定数据源。
      if (this.data.controlTypeID == "3" || this.data.controlTypeID == "11") {
        // 如果值不为空 并且 查询面板是关闭状态，才有查询的意义，且还涉及到item和list控件的reload问题，因为reload以后，value必然为空。
        if (
          !this.octopus.tools.IsNullOrEmpty(this.modelValue) &&
          !this.showRequestPanel
        ) {
          this.request("", this.modelValue);
        }
      }
      // 图像上传控件 || 文件上传控件
      else if (
        this.data.controlTypeID == "15" ||
        this.data.controlTypeID == "16"
      ) {
        if (this.octopus.tools.IsNullOrEmpty(this.modelValue)) {
          this.uploadFileList = [];
        } else {
          this.uploadFileList = JSON.parse(this.modelValue);
        }
      }
    }
    // 针对一些妖孽的控件会在初创的生命周期时自动绑定值，此时change是感知不到的，需要这里反馈一下。
    // modelValue(newv, oldv) {
    //   // 仅适用于ITEM，List有bug，需要调整，估计也是因为这个原因，0无法清空。
    //   if (this.data.model == "ITEM") {
    //     if (this.modelValue != this.data.defaultValue) {
    //       this.change(this.modelValue);
    //     }
    //   }
    // }
  },
  // 此处value一定要加，虽然是vue的保留符。
  props: ["data", "index", "changed", "value"]
};
</script>

<!--1	TextBox	文本框                  【支持】-->
<!--2	TextArea	多行文本框            【支持】-->
<!--3	DropDownList	异步下拉框        【用用户表测试】-->
<!--4	Label	标签框                    【支持】-->
<!--5	Calendar	日历框                【支持】-->
<!--6	CalendarTime	日历时间框        【支持】-->
<!--7	TreeList	树状下拉框            【用用户表测试】-->
<!--8	EmailBox	邮件框                【支持】-->
<!--9	ZipCodeBox	邮编框              【支持】-->
<!--10	HttpBox	网址框                【支持】-->
<!--11	MultipleDownList	异步多选框  【用用户表测试】-->
<!--12	IntegerBox	数字框            【支持】-->
<!--13	FloatBox	小数点框            【支持】-->
<!--14  IDCardBox 身份证框            【用用户表测试】-->
<!--手机号码框-->
<!--电话号码-->
<!--单选框、复选框待开发-->
